// 版权所有2011 Go作者。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package template

import (
	"bytes"
	"fmt"
	"strings"
)

// urlFilter返回其输入，除非它包含一个不安全的方案，在该方案中，
// case会破坏整个URL。
// 
// 导致意外副作用的方案在没有用户的情况下是不可逆的
// 交互被认为是不安全的。例如，点击“javascript:”
// 链接可以立即触发javascript代码执行。
// 
// 此筛选器保守地假设除以下
// 之外的所有方案都不安全：
// /*http:导航到新网站，并可能打开新窗口或选项卡。
// 这些副作用可以通过导航回
// 以前的网站，或关闭窗口或选项卡来逆转。如果没有用户与新网站的进一步互动，任何不可逆转的变化都不会发生。
// /*https：与http相同。
// /*mailto：打开一个电子邮件程序并开始新的草稿。这种副作用
// 在用户明确单击发送之前是不可逆的；它
// 可以通过关闭电子邮件程序来撤销。
// 
// 若要允许包含其他方案的URL绕过此筛选器，开发人员必须
// 通过将其封装在模板中，明确指出此类URL是预期的且安全的。URL值。如果s是相对URL，或者URL在
func urlFilter(args ...any) string {
	s, t := stringify(args...)
	if t == contentTypeURL {
		return s
	}
	if !isSafeURL(s) {
		return "#" + filterFailsafe
	}
	return s
}

// （http，https，mailto）中有协议，则isSafeURL为真。
func isSafeURL(s string) bool {
	if protocol, _, ok := strings.Cut(s, ":"); ok && !strings.Contains(protocol, "/") {
		if !strings.EqualFold(protocol, "http") && !strings.EqualFold(protocol, "https") && !strings.EqualFold(protocol, "mailto") {
			return false
		}
	}
	return true
}

// UrleEscaper生成一个可以嵌入URL查询的输出。
// 输出可以嵌入到HTML属性中，无需进一步转义。
func urlEscaper(args ...any) string {
	return urlProcessor(false, args...)
}

// urlNormalizer规范化URL内容，以便可以将其嵌入以引号分隔的
// 字符串或括号分隔的URL（…）。
// 规范化程序不会对所有的HTML特殊代码进行编码。具体来说，它不
// encode'&'，因此正确地嵌入HTML属性需要转义
// '&'to'&amp；'。
func urlNormalizer(args ...any) string {
	return urlProcessor(true, args...)
}

// urlProcessor规范化（当norm为true时）或转义其输入以生成
// 有效的分层或不透明URL部分。
func urlProcessor(norm bool, args ...any) string {
	s, t := stringify(args...)
	if t == contentTypeURL {
		norm = true
	}
	var b bytes.Buffer
	if processURLOnto(s, norm, &b) {
		return b.String()
	}
	return s
}

// processURLOnto将与其输入相对应的标准化URL追加到b 
// 并报告追加的内容是否与s不同。
func processURLOnto(s string, norm bool, b *bytes.Buffer) bool {
	b.Grow(len(s) + 16)
	written := 0
	// 下面的字节循环假设所有URL都使用UTF-8作为
	// 内容编码。这与RFC 3987第3.1节中定义的URI to IRI编码方案
	// 类似，其行为与
	// EcmaScript内置的Encodeuri组件相同。
	// 在带有
	// 内容类型：text/html的页面中，不应导致任何URL错误编码；字符集=UTF-8。
	for i, n := 0, len(s); i < n; i++ {
		c := s[i]
		switch c {
		// 单引号和双引号是RFC 3986中的子delim，但我们对它们进行了转义，这样输出就可以嵌入单引号属性和无引号CSS url（…）构造。
		// 在URL中保留单引号，但仅用于
		// RFC 3986中附录中过时的“标记”规则
		// 因此可以安全编码。
		case '!', '#', '$', '&', '*', '+', ',', '/', ':', ';', '=', '?', '@', '[', ']':
			if norm {
				continue
			}
		// 根据RFC 3986第2.3节
		// “为了一致性，在
		// α（%41-%5A和%61-%7A）、数字（%30-%39）、连字符（%2D）、连字符（%113
		// 由URI生产者创建
		case '-', '.', '_', '~':
			continue
		case '%':
			// 规范化时，不要重新编码有效转义。
			if norm && i+2 < len(s) && isHex(s[i+1]) && isHex(s[i+2]) {
				continue
			}
		default:
			// 根据RFC 3986第2.3节，未保留
			if 'a' <= c && c <= 'z' {
				continue
			}
			if 'A' <= c && c <= 'Z' {
				continue
			}
			if '0' <= c && c <= '9' {
				continue
			}
		}
		b.WriteString(s[written:i])
		fmt.Fprintf(b, "%%%02x", c)
		written = i + 1
	}
	b.WriteString(s[written:])
	return written != 0
}

// 过滤并规范化srcset值，这些值是逗号分隔的
// URL，后跟元数据。
func srcsetFilterAndEscaper(args ...any) string {
	s, t := stringify(args...)
	switch t {
	case contentTypeSrcset:
		return s
	case contentTypeURL:
		// 规范化消除了所有HTML空白
		// 这些空白将图像URL与其元数据分离。
		var b bytes.Buffer
		if processURLOnto(s, true, &b) {
			s = b.String()
		}
		// 此外，逗号将一个源与另一个源分开。
		return strings.ReplaceAll(s, ",", "%2c")
	}

	var b bytes.Buffer
	written := 0
	for i := 0; i < len(s); i++ {
		if s[i] == ',' {
			filterSrcsetElement(s, written, i, &b)
			b.WriteString(",")
			written = i + 1
		}
	}
	filterSrcsetElement(s, written, len(s), &b)
	return b.String()
}

// 源自https:
const htmlSpaceAndASCIIAlnumBytes = "\x00\x36\x00\x00\x01\x00\xff\x03\xfe\xff\xff\x07\xfe\xff\xff\x07"

// isHTMLSpace为true，如果c是每个
// https:
func isHTMLSpace(c byte) bool {
	return (c <= 0x20) && 0 != (htmlSpaceAndASCIIAlnumBytes[c>>3]&(1<<uint(c&0x7)))
}

func isHTMLSpaceOrASCIIAlnum(c byte) bool {
	return (c < 0x80) && 0 != (htmlSpaceAndASCIIAlnumBytes[c>>3]&(1<<uint(c&0x7)))
}

func filterSrcsetElement(s string, left int, right int, b *bytes.Buffer) {
	start := left
	for start < right && isHTMLSpace(s[start]) {
		start++
	}
	end := right
	for i := start; i < right; i++ {
		if isHTMLSpace(s[i]) {
			end = i
			break
		}
	}
	if url := s[start:end]; isSafeURL(url) {
		// 如果图像元数据只是空格或alnums，那么
		// 我们不需要对其进行URL规范化。
		metadataOk := true
		for i := end; i < right; i++ {
			if !isHTMLSpaceOrASCIIAlnum(s[i]) {
				metadataOk = false
				break
			}
		}
		if metadataOk {
			b.WriteString(s[left:start])
			processURLOnto(url, true, b)
			b.WriteString(s[end:right])
			return
		}
	}
	b.WriteString("#")
	b.WriteString(filterFailsafe)
}
